﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.Reflection;
using System.IO;
using System.Threading;
using System.Windows.Threading;
using System.Disposables;
using System.Diagnostics;

namespace System.Collections.Generic
{
    public static class Extensions
    {
        public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
        {
            Contract.Requires(collection != null);
            Contract.Requires(action != null);

            foreach (var item in collection)
                action(item);
        }

        public static T[] DequeueAll<T>(this Queue<T> queue)
        {
            Contract.Requires(queue != null);
            Contract.Ensures(Contract.Result<T[]>() != null);

            var result = queue.ToArray();
            Contract.Assume(result != null); //TODO: Queue<T>.ToArray contract problem
            queue.Clear();
            return result;
        }
    }
}

namespace System
{
    public static class Extensions
    {
        public static TAttribute GetCustomAttribute<TAttribute>(this MemberInfo member, bool throwOnError = false) where TAttribute : Attribute
        {
            Contract.Requires(member != null);

            var result = (TAttribute)Attribute.GetCustomAttribute(member, typeof(TAttribute));
            if (result != null || !throwOnError)
                return result;
            else
                throw new ApplicationException(String.Format("Attribute {0} was not found on member {1}.", typeof(TAttribute).FullName, member.ToString()));
        }
    }
}

namespace System.IO
{
    public static class Extensions
    {
        public static IObservable<string> ReadToEndAsync(this Stream stream, Encoding encoding)
        {
            Contract.Requires(stream != null);
            Contract.Requires(encoding != null);
            Contract.Ensures(Contract.Result<IObservable<string>>() != null);

            var result = Observable.CreateWithDisposable<string>(
                observer =>
                {
                    // TODO: Глючит контракт
                    //Contract.Requires(observer != null);
                    //Contract.Ensures(Contract.Result<IDisposable>() != null);

                    try
                    {
                        var memoryStream = new MemoryStream();
                        byte[] buffer = new byte[1024];

                        Action func = null;
                        func =
                            () =>
                            {
                                stream.BeginRead(buffer, 0, buffer.Length,
                                    ar =>
                                    {
                                        try
                                        {
                                            int bytesread = stream.EndRead(ar);
                                            if (bytesread == 0)
                                            {
                                                // Seek to the beginning of the stream and read string
                                                memoryStream.Seek(0, SeekOrigin.Begin);
                                                using (var reader = new StreamReader(memoryStream, encoding))
                                                {
                                                    string s = new StreamReader(memoryStream).ReadToEnd();
                                                    observer.OnNext(s);
                                                }
                                                observer.OnCompleted();
                                            }
                                            else
                                            {
                                                memoryStream.Write(buffer, 0, bytesread);
                                                func();
                                            }
                                        }
                                        catch (Exception exception)
                                        {
                                            observer.OnError(exception);
                                        }
                                    },
                                    null);
                            };

                        func();
                    }
                    catch (Exception ex)
                    {
                        observer.OnError(ex);
                    }

                    return Disposable.Empty;
                });
            Contract.Assume(result != null);
            return result;
        }

        public static IObservable<Unit> WriteAsync(this Stream stream, string data, Encoding encoding)
        {
            Contract.Requires(stream != null);
            Contract.Requires(data != null);
            Contract.Requires(encoding != null);
            Contract.Ensures(Contract.Result<IObservable<Unit>>() != null);

            var observableFactory = Observable.FromAsyncPattern<byte[], int, int>(stream.BeginWrite, stream.EndWrite);
            byte[] bytes = encoding.GetBytes(data);
            var result = observableFactory(bytes, 0, bytes.Length);
            Contract.Assume(result != null);
            return result;
        }
    }
}

namespace System.Xml.Serialization
{
    public static class Extensions
    {
        public static T Deserialize<T>(this XmlSerializer serializer, string s) where T: class
        {
            Contract.Requires(serializer != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(s));
            Contract.Ensures(Contract.Result<T>() != null);

            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(s)))
            {
                var result = (T)serializer.Deserialize(stream);
                Contract.Assume(result != null);
                return result;
            }
        }
    }
}

namespace System.Runtime.Serialization.Json
{
    public static class Extensions
    {
        public static object ReadObject(this DataContractJsonSerializer serializer, string s)
        {
            Contract.Requires(serializer != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(s));
            Contract.Ensures(Contract.Result<object>() != null);

            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(s)))
            {
                var result = serializer.ReadObject(stream);
                Contract.Assume(result != null);
                return result;
            }
        }

        public static object ReadObject(this DataContractJsonSerializer serializer, string s, bool throwOnError)
        {
            Contract.Requires(serializer != null);
            Contract.Requires(!String.IsNullOrWhiteSpace(s));

            try
            {
                return ReadObject(serializer, s);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(String.Format("Failed to deserialize \"{0}\".", s));
                Trace.WriteLine(ex.ToString());
                if (throwOnError)
                    throw;
                else
                    return null;
            }
        }
    }
}
